---
title: MCP Hub
---


# MCP Hub Integration

The MCP Hub allows your integration to expose tools that AI agents can use directly. This enables agents to interact with external services through your integration, creating a powerful extension of their capabilities.

## What is MCP Hub?

The MCP (Model Context Protocol) Hub is a centralized way for integrations to expose tools to AI agents. When an integration connects an MCP server, those tools become available to any AI agent using Core.

**Example flows:**
- Agent uses GitHub integration's `create_issue` tool to file a bug
- Agent uses Slack integration's `send_message` tool to notify a team
- Agent uses Linear integration's `create_task` tool to assign work

## MCP Configuration

Configure MCP in your integration's `spec.json`:

### HTTP-based MCP

For services with existing HTTP MCP servers:

```json
{
  "mcp": {
    "type": "http",
    "url": "https://api.githubcopilot.com/mcp/",
    "headers": {
      "Authorization": "Bearer ${config:access_token}",
      "Content-Type": "application/json",
      "X-Custom-Header": "${config:custom_value}"
    }
  }
}
```

**Key points:**
- `type: "http"` - Specifies HTTP transport
- `url` - The MCP server endpoint
- `headers` - HTTP headers for authentication and configuration
- `${config:access_token}` - Placeholder replaced with user's OAuth token at runtime
- Any `${config:key}` placeholder pulls from the user's integration config

### Stdio-based MCP

For command-line MCP servers:

```json
{
  "mcp": {
    "type": "stdio",
    "url": "https://integrations.heysol.ai/slack/mcp/slack-mcp-server",
    "args": [],
    "env": {
      "SLACK_MCP_XOXP_TOKEN": "${config:access_token}",
      "SLACK_MCP_ADD_MESSAGE_TOOL": true,
      "SLACK_WORKSPACE_ID": "${config:team_id}"
    }
  }
}
```

**Key points:**
- `type: "stdio"` - Specifies stdio transport (command-line)
- `url` - Binary/executable URL or path
- `args` - Command-line arguments array
- `env` - Environment variables passed to the process
- Placeholders work in env vars too

## Configuration Placeholders

The `${config:key}` syntax pulls values from the user's integration configuration:

### Setup Phase

When user completes OAuth, you set the config:

```typescript
// account-create.ts
export async function integrationCreate(data: any) {
  const { oauthResponse } = data;

  return [{
    type: 'account',
    data: {
      accountId: user.id.toString(),
      config: {
        access_token: oauthResponse.access_token,
        refresh_token: oauthResponse.refresh_token,
        team_id: user.team_id,
        custom_setting: 'value',
        // This config is accessible in MCP as ${config:*}
        mcp: {
          tokens: {
            access_token: oauthResponse.access_token
          }
        }
      }
    }
  }];
}
```

### Runtime Phase

Core replaces placeholders when connecting to MCP server:

```json
// Before replacement (spec.json)
{
  "mcp": {
    "type": "http",
    "url": "https://api.service.com/mcp/",
    "headers": {
      "Authorization": "Bearer ${config:access_token}",
      "X-Team-ID": "${config:team_id}"
    }
  }
}

// After replacement (runtime)
{
  "mcp": {
    "type": "http",
    "url": "https://api.service.com/mcp/",
    "headers": {
      "Authorization": "Bearer xoxp-actual-token-here",
      "X-Team-ID": "T12345"
    }
  }
}
```

## HTTP MCP Server Example (GitHub)

### spec.json

```json
{
  "name": "GitHub extension",
  "key": "github",
  "description": "Manage GitHub repositories and issues",
  "icon": "github",

  "auth": {
    "OAuth2": {
      "token_url": "https://github.com/login/oauth/access_token",
      "authorization_url": "https://github.com/login/oauth/authorize",
      "scopes": ["repo", "user"]
    }
  },

  "mcp": {
    "type": "http",
    "url": "https://api.githubcopilot.com/mcp/",
    "headers": {
      "Authorization": "Bearer ${config:access_token}",
      "Content-Type": "application/json"
    }
  }
}
```

### Available Tools

When Core connects to GitHub's MCP server, these tools become available:

- `github_create_issue` - Create a new issue
- `github_search_code` - Search code across repositories
- `github_create_pull_request` - Create a new PR
- `github_list_commits` - List commits for a repository

Agents can now call these tools:

```typescript
// Agent calls this internally
await mcpClient.callTool('github_create_issue', {
  owner: 'user',
  repo: 'project',
  title: 'Bug: Login fails',
  body: 'Steps to reproduce...'
});
```

## Stdio MCP Server Example (Slack)

### spec.json

```json
{
  "name": "Slack extension",
  "key": "slack",
  "description": "Connect your workspace to Slack",
  "icon": "slack",

  "auth": {
    "OAuth2": {
      "token_url": "https://slack.com/api/oauth.v2.access",
      "authorization_url": "https://slack.com/oauth/v2/authorize",
      "scopes": ["chat:write", "channels:read", "users:read"]
    }
  },

  "mcp": {
    "type": "stdio",
    "url": "https://integrations.heysol.ai/slack/mcp/slack-mcp-server",
    "args": [],
    "env": {
      "SLACK_MCP_XOXP_TOKEN": "${config:access_token}",
      "SLACK_MCP_ADD_MESSAGE_TOOL": true
    }
  }
}
```

### Available Tools

The Slack MCP server exposes:

- `slack_send_message` - Send message to channel
- `slack_list_channels` - List workspace channels
- `slack_add_reaction` - React to a message
- `slack_get_users` - Get workspace users

## Building Your Own MCP Server

If your service doesn't have an existing MCP server, you can build one:

### 1. Use the MCP SDK

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server(
  {
    name: 'my-service-mcp',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Define your tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'create_task',
        description: 'Create a new task',
        inputSchema: {
          type: 'object',
          properties: {
            title: { type: 'string' },
            description: { type: 'string' },
          },
          required: ['title'],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === 'create_task') {
    const result = await createTask(args.title, args.description);
    return {
      content: [{ type: 'text', text: `Created task: ${result.id}` }],
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
```

### 2. Authentication

Get tokens from environment variables:

```typescript
const accessToken = process.env.MY_SERVICE_TOKEN;

if (!accessToken) {
  throw new Error('MY_SERVICE_TOKEN not provided');
}

// Use token in API calls
const response = await fetch('https://api.myservice.com/tasks', {
  headers: {
    Authorization: `Bearer ${accessToken}`
  }
});
```

### 3. Expose in spec.json

```json
{
  "mcp": {
    "type": "stdio",
    "url": "/path/to/your-mcp-server",
    "args": [],
    "env": {
      "MY_SERVICE_TOKEN": "${config:access_token}"
    }
  }
}
```

## MCP Tool Best Practices

### 1. Clear Tool Descriptions

```json
{
  "name": "create_issue",
  "description": "Create a new GitHub issue in a repository. Returns the issue number and URL.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "owner": {
        "type": "string",
        "description": "Repository owner (username or organization)"
      },
      "repo": {
        "type": "string",
        "description": "Repository name"
      },
      "title": {
        "type": "string",
        "description": "Issue title (required, max 256 characters)"
      },
      "body": {
        "type": "string",
        "description": "Issue description in markdown format"
      }
    },
    "required": ["owner", "repo", "title"]
  }
}
```

### 2. Return Structured Data

```typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const result = await createIssue(args);

  return {
    content: [
      {
        type: 'text',
        text: `Created issue #${result.number}: ${result.html_url}`
      },
      {
        type: 'resource',
        resource: {
          uri: result.html_url,
          mimeType: 'text/html',
          text: JSON.stringify(result)
        }
      }
    ],
  };
});
```

### 3. Error Handling

```typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const result = await performAction(args);
    return {
      content: [{ type: 'text', text: `Success: ${result}` }],
    };
  } catch (error) {
    return {
      content: [{
        type: 'text',
        text: `Error: ${error.message}`
      }],
      isError: true,
    };
  }
});
```

### 4. Rate Limiting

```typescript
let lastCallTime = 0;
const MIN_INTERVAL = 1000; // 1 second between calls

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const now = Date.now();
  if (now - lastCallTime < MIN_INTERVAL) {
    await new Promise(resolve =>
      setTimeout(resolve, MIN_INTERVAL - (now - lastCallTime))
    );
  }
  lastCallTime = Date.now();

  // Proceed with tool call
});
```

## Testing MCP Tools

### 1. Test with MCP Inspector

```bash
npx @modelcontextprotocol/inspector node your-mcp-server.js
```

### 2. Test with Claude Desktop

Add to Claude Desktop config:

```json
{
  "mcpServers": {
    "your-service": {
      "command": "node",
      "args": ["/path/to/your-mcp-server.js"],
      "env": {
        "MY_SERVICE_TOKEN": "test-token"
      }
    }
  }
}
```

### 3. Test Tool Calls

```typescript
// In your integration tests
const client = new Client({
  name: 'test-client',
  version: '1.0.0'
}, {
  capabilities: {
    tools: {}
  }
});

await client.connect(transport);

const tools = await client.listTools();
console.log('Available tools:', tools);

const result = await client.callTool('create_task', {
  title: 'Test task'
});
console.log('Tool result:', result);
```

## Common Patterns

### Dynamic Tool Listing

Generate tools based on user's account:

```typescript
server.setRequestHandler(ListToolsRequestSchema, async () => {
  const user = await getUserFromToken(accessToken);
  const projects = await getProjects(user.id);

  // Generate tool for each project
  const tools = projects.map(project => ({
    name: `create_task_${project.id}`,
    description: `Create task in ${project.name}`,
    inputSchema: {
      type: 'object',
      properties: {
        title: { type: 'string' }
      }
    }
  }));

  return { tools };
});
```

### Composite Actions

Combine multiple API calls into one tool:

```typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'create_issue_with_assignee') {
    // 1. Create issue
    const issue = await createIssue(args.title, args.body);

    // 2. Assign user
    await assignIssue(issue.number, args.assignee);

    // 3. Add labels
    await addLabels(issue.number, args.labels);

    return {
      content: [{
        type: 'text',
        text: `Created and configured issue #${issue.number}`
      }],
    };
  }
});
```

## Security Considerations

1. **Token Scoping** - Only request OAuth scopes needed for MCP tools
2. **Input Validation** - Validate all tool parameters before use
3. **Rate Limiting** - Implement rate limits to prevent abuse
4. **Error Messages** - Don't expose sensitive data in error messages
5. **Token Storage** - Tokens are securely encrypted in Core's database

## Next Steps

- [Build a Complete Integration](/integrations/building-integrations)
- [View MCP SDK Documentation](https://modelcontextprotocol.io/docs)
- [See Example MCP Servers](https://github.com/modelcontextprotocol/servers)
- [Explore GitHub MCP Server](https://github.com/github/github-mcp-server)
